package com.hd123.rumba.runtime;

import java.awt.Color;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import java.util.Vector;

import javax.servlet.ServletConfig;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import net.sf.hibernate.util.DTDEntityResolver;

import org.apache.log4j.Logger;
import org.dom4j.Attribute;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.w3c.dom.Document;
import org.xml.sax.EntityResolver;

import com.hd123.rumba.cartridge.FVRumbaSystem;
import com.hd123.rumba.cartridge.biz.IBCartridgeMgr;
import com.hd123.rumba.domain.IDomain;
import com.hd123.rumba.domain.IDomainMgr;
import com.hd123.rumba.log.ProcessDBAppender;
import com.hd123.rumba.log.RumbaLogger;
import com.hd123.rumba.qt.QTConstants;
import com.hd123.rumba.qt.QueryResultSession;
import com.hd123.rumba.qt.QuerySourceSession;
import com.hd123.rumba.qt.task.QueryTaskProcess;
import com.hd123.rumba.report.IReportProvider;
import com.hd123.rumba.report.ReportFactory;
import com.hd123.rumba.request.ActionConstants;
import com.hd123.rumba.runtime.backprocess.DailySchedule;
import com.hd123.rumba.runtime.backprocess.TaskNature;
import com.hd123.rumba.user.IUser;
import com.hd123.rumba.user.IUserMgr;

public class Configuration {

  private static Configuration instance = null;

  public static Configuration getInstance() {
    if (instance == null)
      instance = new Configuration();
    return instance;
  }

  /** 构件包前缀 */
  public static final String FUNCV_PREFIX = FVRumbaSystem.FUNCV_PREFIX;

  /**
   * servlet的初始化参数中应有形如<br>
   * &lt;configurations&gt;/rumba.xml;/myrumba.xml&lt;/configurations&gt;<br>
   * 的说明
   * 
   * @param cfg
   * @throws Exception
   */
  public Configuration config(ServletConfig cfg) throws Exception {
    String resources = cfg.getInitParameter("configurations");
    
    URL url = cfg.getServletContext().getResource("/WEB-INF/web.xml");
    String[] words = url.getPath().split("/");
    String appHome = words[2];
    if (words.length < 5)
      appHome = "";
    ActionConstants.APPHOME = appHome;
    
    ActionConstants.APPHOMEREALPATH = new File(cfg.getServletContext().getRealPath("."))
        .getParent();
    
    servletConfig = cfg;
    
    return config(resources, appHome);
  }

  /**
   * @param resources
   *          分号分隔的配置文件名
   * @param appHome
   * @throws Exception
   */
  public Configuration config(String resources, String appHome) throws Exception {
    this.appHome = appHome;
    return config(resources);
  }

  public Configuration config(String resources) throws Exception {
    merge(resources);
    doConfig();
    return this;
  }

  public Configuration merge(String resources) throws Exception {
    this.globalResources = resources;
    this.resourceDocuments = new ArrayList();

    StringTokenizer st = new StringTokenizer(resources, ";");
    while (st.hasMoreTokens()) {
      String resourceName = st.nextToken();
      logger.info("读取配置文件: " + resourceName);
      InputStream is = Configuration.class.getResourceAsStream(resourceName);
      if (is == null)
        is = new FileInputStream(resourceName);
      if (is == null)
        throw new Exception("未能读取配置文件: " + resourceName);
      org.dom4j.Document doc = new SAXReader().read(is);
      this.resourceDocuments.add(doc);
      mergeOne(doc);
    }
    return this;
  }

  public String getBusinessXml() {
    return getHibernateXml(businessOverride, businessBuffer);
  }

  public Document getBusinessDoc() throws Exception {
    return getHibernateDoc(getBusinessXml());
  }

  public String getQuerySourceXml() {
    return getHibernateXml(querySourceOverride, querySourceBuffer);
  }

  public Document getQuerySourceDoc() throws Exception {
    return getHibernateDoc(getQuerySourceXml());
  }

  public String getQueryResultXml() {
    return getHibernateXml(queryResultOverride, queryResultBuffer);
  }

  public Document getQueryResultDoc() throws Exception {
    return getHibernateDoc(getQueryResultXml());
  }

  public List<String> getWsddList() {
    return wsddList;
  }

  public List<String> getCartridgeList() {
    return cartridgeList;
  }

  public List<String> getBuffaloList() {
    return buffaloList;
  }

  public String debugInfoXml() {
    StringBuffer sb = new StringBuffer();
    sb.append("<?xml version='1.0' encoding='gb2312'?>\n");
    sb.append("<hibernates\n");
    sb.append("<business>\n");
    sb.append(debugInfoHibernateXml(businessOverride, businessBuffer));
    sb.append("</business>\n");
    sb.append("<querySource>\n");
    sb.append(debugInfoHibernateXml(querySourceOverride, querySourceBuffer));
    sb.append("</querySource>\n");
    sb.append("<queryResult>\n");
    sb.append(debugInfoHibernateXml(queryResultOverride, queryResultBuffer));
    sb.append("</queryResult>\n");
    sb.append("</hibernates>\n");
    sb.append(debugInfoImplementationsXml());
    sb.append(debugInfoPropertiesXml());
    sb.append(debugInfoCustomXml());
    return sb.toString();
  }

  public void setProperties() throws Exception {
    Hashtable<String, Hashtable<String, String>> properties = getProperties();
    Enumeration<String> classNames = properties.keys();
    while (classNames.hasMoreElements()) {
      String className = classNames.nextElement();
      Hashtable<String, String> h = properties.get(className);
      Enumeration<String> fieldNames = h.keys();
      Class clazz = Class.forName(className);
      logger.info("设置类" + className + "属性值");
      while (fieldNames.hasMoreElements()) {
        String fieldName = fieldNames.nextElement();
        String stringValue = h.get(fieldName);
        setFieldValue(clazz, fieldName, stringValue);
      }
    }
  }

  public String getGlobalResources() {
    return globalResources;
  }

  public List<org.dom4j.Document> getResourceDocuments() throws Exception {
    if (this.resourceDocuments != null)
      return this.resourceDocuments;

    this.resourceDocuments = new ArrayList();
    StringTokenizer st = new StringTokenizer(this.globalResources, ";");
    while (st.hasMoreTokens()) {
      String resourceName = st.nextToken();
      logger.info("载入配置文件: " + resourceName);
      InputStream is = Configuration.class.getResourceAsStream(resourceName);
      if (is == null)
        is = new FileInputStream(resourceName);
      if (is == null)
        throw new Exception("未能载入配置文件: " + resourceName);
      org.dom4j.Document doc = new SAXReader().read(is);
      this.resourceDocuments.add(doc);
    }
    return this.resourceDocuments;
  }

  public void freeResourceDocuments() {
    this.resourceDocuments = null;
  }

  /**
   * @return 返回的hashtable的key=interface类名, value=实现类名
   */
  private Hashtable<String, String> getImplementations() {
    return implementationTable;
  }

  /**
   * @return 返回的hashtable的key = 类名, value = hashtable <br>
   *         value hashtable的key=属性名, value=属性值
   */
  private Hashtable<String, Hashtable<String, String>> getProperties() {
    return propertyTable;
  }

  public String getHibernateXml(KeyValueVector<String, String> override, StringBuffer asis) {
    StringBuffer sb = new StringBuffer();
    sb.append("<?xml version='1.0' encoding='utf-8'?>\n");
    sb.append("<!DOCTYPE hibernate-configuration PUBLIC\n");
    sb.append("\"-//Hibernate/Hibernate Configuration DTD 2.0//EN\"\n");
    sb.append("\"http://hibernate.sourceforge.net/hibernate-configuration-2.0.dtd\">\n");
    sb.append(debugInfoHibernateXml(override, asis));
    return sb.toString();
  }

  public Document getHibernateDoc(String xml) throws Exception {
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    DocumentBuilder builder = factory.newDocumentBuilder();
    builder.setEntityResolver(DTD_RESOLVER);
    return builder.parse(new ByteArrayInputStream(xml.getBytes()));
  }

  private EntityResolver DTD_RESOLVER = new DTDEntityResolver();

  private String debugInfoHibernateXml(KeyValueVector<String, String> override, StringBuffer asis) {
    StringBuffer sb = new StringBuffer();
    sb.append("<hibernate-configuration>\n");
    sb.append("<session-factory>\n");
    Enumeration e = override.keys();
    while (e.hasMoreElements()) {
      sb.append(override.get(e.nextElement()));
      sb.append("\n");
    }
    sb.append(asis);
    sb.append("</session-factory>\n");
    sb.append("</hibernate-configuration>\n");
    return sb.toString();
  }

  private String debugInfoImplementationsXml() {
    StringBuffer sb = new StringBuffer();
    sb.append("<implementations>\n");
    Enumeration<String> keys = getImplementations().keys();
    while (keys.hasMoreElements()) {
      String name = keys.nextElement();
      String value = getImplementations().get(name);
      sb.append("<property name=\"");
      sb.append(name);
      sb.append("\">");
      sb.append(value);
      sb.append("</property>\n");
    }
    sb.append("</implementations>\n");
    return sb.toString();
  }

  private String debugInfoPropertiesXml() {
    StringBuffer sb = new StringBuffer();
    Enumeration<String> classNames = getProperties().keys();
    while (classNames.hasMoreElements()) {
      String className = classNames.nextElement();
      Hashtable<String, String> h = getProperties().get(className);
      sb.append("<properties name=\"");
      sb.append(className);
      sb.append("\">\n");

      Enumeration<String> keys = h.keys();
      while (keys.hasMoreElements()) {
        String name = keys.nextElement();
        String value = h.get(name);
        sb.append("<property name=\"");
        sb.append(name);
        sb.append("\">");
        sb.append(value);
        sb.append("</property>\n");
      }
      sb.append("</properties>\n");
    }
    return sb.toString();
  }

  private String debugInfoCustomXml() {
    StringBuffer sb = new StringBuffer();
    for (String custom : this.customList) {
      sb.append("<custom name=\"" + custom + "\"\n");
    }
    return sb.toString();
  }

  private Logger logger = Logger.getLogger(Configuration.class);

  private KeyValueVector<String, String> businessOverride = new KeyValueVector();
  private StringBuffer businessBuffer = new StringBuffer();
  private KeyValueVector<String, String> querySourceOverride = new KeyValueVector();
  private StringBuffer querySourceBuffer = new StringBuffer();
  private KeyValueVector<String, String> queryResultOverride = new KeyValueVector();
  private StringBuffer queryResultBuffer = new StringBuffer();
  private Hashtable<String, String> implementationTable = new Hashtable();
  private List<String> customList = new ArrayList();
  private String globalResources; // 分号分隔的配置文件名
  private Hashtable<String, Hashtable<String, String>> propertyTable = new Hashtable();
  private String appHome = "";
  private List<String> wsddList = new ArrayList();
  private List<String> cartridgeList = new ArrayList();
  private List<String> buffaloList = new ArrayList();
  private List<String> reportList = new ArrayList();
  ServletConfig servletConfig = null;
  private IgnoreElement bBusiness = new IgnoreElement();
  private IgnoreElement bQuerySource = new IgnoreElement();
  private IgnoreElement bQueryResult = new IgnoreElement();
  private List<org.dom4j.Document> resourceDocuments = null;

  private void mergeOne(org.dom4j.Document doc) throws Exception {
    Iterator elements = doc.getRootElement().elementIterator();
    while (elements.hasNext()) {
      Element element = (Element) elements.next();
      String elementName = element.getName();
      if ("hibernates".equals(elementName)) {
        bBusiness.merge(processHibernateCfg(element.element("business"), businessOverride,
            businessBuffer));
        bQuerySource.merge(processHibernateCfg(element.element("querySource"), querySourceOverride,
            querySourceBuffer));
        bQueryResult.merge(processHibernateCfg(element.element("queryResult"), queryResultOverride,
            queryResultBuffer));
      } else if ("custom".equals(elementName)) {
        Attribute clazz = element.attribute("class");
        if (clazz != null) {
          String custom = clazz.getValue().trim();
          if (!"".equals(custom))
            this.customList.add(clazz.getValue());
        }
      } else if ("implementations".equals(elementName)) {
        Iterator properties = element.elementIterator();
        while (properties.hasNext()) {
          Element property = (Element) properties.next();
          Attribute name = property.attribute("name");
          implementationTable.put(name.getValue(), property.getTextTrim());
        }
      } else if ("properties".equals(elementName)) {
        Attribute clazz = element.attribute("class");
        Hashtable<String, String> p = propertyTable.get(clazz.getValue());
        if (p == null) {
          p = new Hashtable();
          propertyTable.put(clazz.getValue(), p);
        }
        Iterator properties = element.elementIterator();
        while (properties.hasNext()) {
          Element property = (Element) properties.next();
          Attribute name = property.attribute("name");
          p.put(name.getValue(), property.getTextTrim());
        }
      } else if ("wsdd".equals(elementName)) {
        Iterator i = element.elementIterator();
        while (i.hasNext()) {
          Element e = (Element) i.next();
          wsddList.add(e.getTextTrim());
        }
      } else if ("cartridges".equals(elementName)) {
        Iterator i = element.elementIterator();
        while (i.hasNext()) {
          Element e = (Element) i.next();
          cartridgeList.add(e.getTextTrim());
        }
      } else if ("buffalo".equals(elementName)) {
        Iterator i = element.elementIterator();
        while (i.hasNext()) {
          Element e = (Element) i.next();
          buffaloList.add(e.getTextTrim());
        }
      } else if ("reports".equals(elementName)) {
        Iterator i = element.elementIterator();
        while (i.hasNext()) {
          Element e = (Element) i.next();
          reportList.add(e.getTextTrim());
        }
      }
    }
  }

  public IgnoreElement processHibernateCfg(Element element, KeyValueVector override,
      StringBuffer buffer) {
    IgnoreElement result = new IgnoreElement();
    if (element == null) {
      result.element = false;
      return result;
    } else
      result.element = true;
    Element ignore = element.element("ignore");
    if (ignore != null) {
      if ("true".equalsIgnoreCase(ignore.getTextTrim())) {
        result.ignore = true;
        return result;
      }
    } else
      result.ignore = false;
    Iterator iterator = element.elementIterator();
    while (iterator.hasNext()) {
      Element e = (Element) iterator.next();
      if ("div".equals(e.getName())) {
        Attribute name = e.attribute("name");
        override.put(name.getValue(), e.asXML());
        Iterator i = e.elementIterator();
        StringBuffer sb = new StringBuffer();
        while (i.hasNext()) {
          Element ie = (Element) i.next();
          sb.append(ie.asXML());
          sb.append("\n");
        }
        override.put(name.getValue(), sb.toString());
      } else {
        buffer.append(e.asXML());
      }
    }
    return result;
  }

  private void doConfig() throws Exception {

    // set NamingMgr table
    logger.info("设置名字管理器");
    Hashtable<String, String> impls = getImplementations();
    Enumeration<String> keys = impls.keys();
    NamingMgr nm = NamingMgr.getInstance();
    while (keys.hasMoreElements()) {
      String intf = keys.nextElement();
      String impl = impls.get(intf);
      nm.registerManager(intf, impl);
    }
    // set Properties
    setProperties();

    // set hibernate
    if (bBusiness.hasEffect()) {
      logger.info("创建业务数据库连接");
      HibernateSession.buildSessionFactory(getBusinessDoc());
    }
    if (bQuerySource.hasEffect()) {
      logger.info("创建查询数据源数据库连接");
      QuerySourceSession.buildSessionFactory(getQuerySourceDoc());
    }
    if (bQueryResult.hasEffect()) {
      logger.info("创建查询结果数据库连接");
      QueryResultSession.buildSessionFactory(getQueryResultDoc());
    }

    // Logger
    if (servletConfig != null) {
      RumbaLogger rootLogger = RumbaLogger.getRootLogger();
      rootLogger.addAppender(new ProcessDBAppender());
    }

    // Cartridge
    logger.info("Cartridge：开始加载");
    IBCartridgeMgr cartMgr = (IBCartridgeMgr) nm.lookupMgr(IBCartridgeMgr.class);
    cartMgr.registerCartridges(cartridgeList);
    if (servletConfig != null) // Web服务启动
      cartMgr.verifyCartridges();
    logger.info("Cartridge：加载完成");

    // Reports
    logger.info("注册报表");
    for (int i = 0; i < reportList.size(); i++) {
      Class clazz = Class.forName(reportList.get(i));
      ReportFactory.registerProvider((IReportProvider) clazz.newInstance());
    }

    // copy WEB-INF/classes/server-config.wsdd to WEB-INF
    // 其它的部署由AxisRequestHandler#init()完成. 所以这个要在其之前被调用
    if (servletConfig != null) {
      logger.info("创建WEB服务配置");
      File webInfo = new File(servletConfig.getServletContext().getRealPath("/WEB-INF"));
      File source = new File(webInfo, "classes/server-config.wsdd");
      File dest = new File(webInfo, "server-config.wsdd");
      FileOutputStream out = new FileOutputStream(dest);
      FileInputStream ins = new FileInputStream(source);
      byte abyte0[] = new byte[2048];
      int bytesRead, len = 0;
      while (-1 != (bytesRead = ins.read(abyte0, 0, abyte0.length))) {
        len += bytesRead;
        out.write(abyte0, 0, bytesRead);
      }
      out.close();
      ins.close();
    }

    // call customized initializer
    for (String custom : this.customList) {
      logger.info("调用定制的初始化程序: " + custom);
      Object boot = Class.forName(custom).newInstance();
      if (boot instanceof IBoot)
        ((IBoot) boot).execute(servletConfig);
    }

    // 定时任务
    if (servletConfig != null) {
      logger.info("启动定时任务开始");
      IDomainMgr domainMgr = (IDomainMgr) NamingMgr.getInstance().lookupMgr(IDomainMgr.class);
      IDomain rootDomain = domainMgr.getRootDomain();
      IUserMgr userMgr = (IUserMgr) NamingMgr.getInstance().lookupMgr(IUserMgr.class);
      IUser operator = userMgr.getSysUser();

      TaskNature tn = TaskNature.create(QueryTaskProcess.class, "查询结果清除任务");
      tn.setDomain(rootDomain);
      tn.setOperator(operator);
      com.hd123.rumba.runtime.backprocess.TaskMgr.getInstance().registerTask(tn,
          new DailySchedule(0, 5, 0));
//
//      tn = TaskNature.create(QueryBITaskProcess.class, "BI数据内存清除任务");
//      tn.setDomain(rootDomain);
//      tn.setOperator(operator);
//      com.hd123.rumba.runtime.backprocess.TaskMgr.getInstance().registerTask(tn,
//          new DailySchedule(16, 42, 0));
      logger.info("启动定时任务完成");
    }

    this.freeResourceDocuments();
    HibernateSession.closeSession();
  }

  private void setFieldValue(Class clazz, String fieldName, String stringValue) throws Exception {
    Object fieldValue = null;
    stringValue = stringValue.replace("{$apphome}", appHome);
    try {
      Field field = clazz.getDeclaredField(fieldName);
      // boolean, byte, char, short, int, long, float, and double.
      Class type = field.getType();
      if (type.isPrimitive()) {
        if ("boolean".equals(type.getName()))
          fieldValue = new Boolean(stringValue);
        else if ("byte".equals(type.getName()))
          fieldValue = new Byte(stringValue);
        else if ("char".equals(type.getName()))
          fieldValue = new Character(stringValue.charAt(0));
        else if ("short".equals(type.getName()))
          fieldValue = new Short(stringValue);
        else if ("int".equals(type.getName()))
          fieldValue = new Integer(stringValue);
        else if ("long".equals(type.getName()))
          fieldValue = new Long(stringValue);
        else if ("float".equals(type.getName()))
          fieldValue = new Float(stringValue);
        else if ("double".equals(type.getName()))
          fieldValue = new Double(stringValue);
      } else if (type.equals(Color.class)) {
        fieldValue = new Color(Integer.parseInt(stringValue, 16));
      } else {
        Constructor c = type.getConstructor(new Class[] {
          String.class });
        fieldValue = c.newInstance(new Object[] {
          stringValue });
      }
      field.set(null, fieldValue);
    } catch (Exception e) {
      logger.error("setFieldValue(" + clazz.getName() + ",\"" + fieldName + "\",\"" + stringValue
          + "\")");
      throw e;
    }
  }

  private Configuration() {
    super();
  }

  public static void main(String[] args) throws Exception {
    Configuration t = new Configuration();

    t.config("/rumba.xml;/myrumba.xml", "eos");
    System.out.println(ActionConstants.APPHOME);
    System.out.println(QTConstants.QRLIFE);

    // System.out.println("======================================");
    // System.out.println("debug info as xml");
    // System.out.println("======================================");
    // System.out.println(t.debugInfoXml());
    //
    // System.out.println("======================================");
    // System.out.println("hibernate business");
    // System.out.println("======================================");
    // System.out.println(t.getBusinessXml());
    //
    // System.out.println("======================================");
    // System.out.println("hibernate query source");
    // System.out.println("======================================");
    // System.out.println(t.getQuerySourceXml());

    // System.out.println("======================================");
    // System.out.println("hibernate query result");
    // System.out.println("======================================");
    // System.out.println(t.getQueryResultXml());
  }

  public static final String PERMISSION_ADMIN = "系统.管理";

  /**
   * 2006-6-12 为了使得override按照原来的次序, 不能使用Hashtable, 而使用KeyValueVector
   * 
   * @author caili
   * 
   * @param <K>
   * @param <V>
   */
  public static class KeyValueVector<K, V> extends Vector<KeyValue> {
    private void put(Object key, Object value) {
      boolean replaced = false;
      for (int i = 0; i < size(); ++i) {
        if (((KeyValue) get(i)).key.equals(key)) {
          ((KeyValue) get(i)).value = value;
          replaced = true;
          break;
        }
      }
      if (!replaced)
        add(new KeyValue(key, value));
    }

    private Enumeration<K> keys() {
      return new Enumeration() {
        private int index = 0;

        public boolean hasMoreElements() {
          return size() > index;
        }

        public K nextElement() {
          return (K) get(index++).key;
        }
      };
    }

    private V get(Object key) {
      for (int i = 0; i < size(); ++i) {
        if (get(i).key.equals(key))
          return (V) get(i).value;
      }
      return null;
    }
  }

  private static class KeyValue<K, V> {
    K key;
    V value;

    private KeyValue(K key, V value) {
      this.key = key;
      this.value = value;
    }

  }

  /**
   * @author caili
   * 
   */
  public class IgnoreElement {
    // A---------B---------C---------D---------E---------F-------------G---------------H
    // 1-element-1-ignore--2-element-2-ignore--element---ignore--------result----------COUNT
    // FALSE-----N/A-------FALSE-----N/A-------FALSE-----N/A-----------FALSE-----------4
    // FALSE-----N/A-------TRUE------TRUE------TRUE------TRUE----------FALSE-----------2
    // FALSE-----N/A-------TRUE------FALSE-----TRUE------FALSE---------TRUE------------2
    // TRUE------TRUE------FALSE-----N/A-------TRUE------TRUE----------FALSE-----------2
    // TRUE------FALSE-----FALSE-----N/A-------TRUE------FALSE---------TRUE------------2
    // TRUE------TRUE------TRUE------TRUE------TRUE------TRUE----------FALSE-----------1
    // TRUE------TRUE------TRUE------FALSE-----TRUE------FALSE---------TRUE------------1
    // TRUE------FALSE-----TRUE------TRUE------TRUE------TRUE----------FALSE-----------1
    // TRUE------FALSE-----TRUE------FALSE-----TRUE------FALSE---------TRUE------------1
    // ----------------------------------------A-OR-C----C-?-D-:-KEEP--E-?-!F-:-FALSE--16
    boolean ignore = false;
    boolean element = false;

    void merge(IgnoreElement e) {
      element = element || e.element;
      ignore = e.element ? e.ignore : ignore;
    }

    boolean hasEffect() {
      return element ? !ignore : false;
    }
  }
}
